#include "L2DataSet.hpp"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

DataSet::~DataSet(){
    this->clear();
}

/************
 *  @fn     bool DataSet::add(const char *label,bool value)
 *  @brief  Add boolean data with label into dataset.
 *  @param  label   Data label.
 *  @param  value   Data value.
 *  @return Result of add operation. true - Add success. false - Add failed.
 */
bool DataSet::add(const char *label,bool value){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    if ( this->hasLabel(label) ){
        this->error_number = Error_AddDataWithExistLabel;
        return false;
    }
    /* Allocate data node. */
    Data *data = new Data;
    if ( data == nullptr ){
        this->error_number = Error_MallocFailed;
        return false;
    }
    /* Allocate memory of label and data in one time. It can reduce memory fragmentation. */
    data->data_type = DataType::Boolean;
    data->label_size = strlen(label) + 1;
    data->value_size = sizeof(bool);
    int malloc_size = data->label_size + data->value_size;
    unsigned char *buffer = new unsigned char[malloc_size];
    if ( buffer == nullptr ){
        delete data;
        data = nullptr;
        this->error_number = Error_MallocFailed;
        return false;
    }
    data->label = (char*)buffer;
    strcpy(data->label,label);
    data->value = (char*)buffer + data->label_size;
    memcpy(data->value,&value,sizeof(bool));
    if ( !this->dataset.append(data) ){
        this->error_number = Error_DataSetError;
        delete[] buffer;
        buffer = nullptr;
        delete data;
        data = nullptr;
        return false;
    }
    this->error_number = Error_None;
    return true;
}

/************
 *  @fn     bool DataSet::add(const char *label,double value)
 *  @brief  Add number data with label into dataset.
 *  @param  label   Data label.
 *  @param  value   Data value.
 *  @return Result of add operation. true - Add success. false - Add failed.
 */
bool DataSet::add(const char *label,double value){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    if ( this->hasLabel(label) ){
        this->error_number = Error_AddDataWithExistLabel;
        return false;
    }
    /* Allocate data node. */
    Data *data = new Data;
    if ( data == nullptr ){
        this->error_number = Error_MallocFailed;
        return false;
    }
    /* Allocate memory of label and data in one time. It can reduce memory fragmentation. */
    data->data_type = DataType::Number;
    data->label_size = strlen(label) + 1;
    data->value_size = sizeof(double);
    int malloc_size = data->label_size + data->value_size;
    unsigned char *buffer = new unsigned char[malloc_size];
    if ( buffer == nullptr ){
        delete data;
        data = nullptr;
        this->error_number = Error_MallocFailed;
        return false;
    }
    data->label = (char*)buffer;
    strcpy(data->label,label);
    data->value = (char*)buffer + data->label_size;
    memcpy(data->value,&value,sizeof(double));
    if ( !this->dataset.append(data) ){
        this->error_number = Error_DataSetError;
        delete[] buffer;
        buffer = nullptr;
        delete data;
        data = nullptr;
        return false;
    }
    this->error_number = Error_None;
    return true;
}

/************
 *  @fn     bool DataSet::add(const char *label,const char *value)
 *  @brief  Add string data with label into dataset.
 *  @param  label   Data label.
 *  @param  value   Data value.
 *  @return Result of add operation. true - Add success. false - Add failed.
 */
bool DataSet::add(const char *label,const char *value){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    if ( value == nullptr ){
        this->error_number = Error_NullString;
        return false;
    }
    if ( this->hasLabel(label) ){
        this->error_number = Error_AddDataWithExistLabel;
        return false;
    }
    /* Allocate data node. */
    Data *data = new Data;
    if ( data == nullptr ){
        this->error_number = Error_MallocFailed;
        return false;
    }
    /* Allocate memory of label and data in one time. It can reduce memory fragmentation. */
    data->data_type = DataType::String;
    data->label_size = strlen(label) + 1;
    data->value_size = strlen(value) + 1;
    int malloc_size = data->label_size + data->value_size;
    malloc_size = data->label_size + data->value_size;
    unsigned char *buffer = new unsigned char[malloc_size];
    if ( buffer == nullptr ){
        delete data;
        data = nullptr;
        this->error_number = Error_MallocFailed;
        return false;
    }
    data->label = (char*)buffer;
    strcpy(data->label,label);
    data->value = (char*)buffer + data->label_size;
    strncpy((char*)data->value,value,data->value_size);
    if ( !this->dataset.append(data) ){
        this->error_number = Error_DataSetError;
        delete[] buffer;
        buffer = nullptr;
        delete data;
        data = nullptr;
        return false;
    }
    this->error_number = Error_None;
    return true;
}

/************
 *  @fn     bool DataSet::remove(const char *label)
 *  @brief  Remove data by label from dataset.
 *  @param  label   Data label.
 *  @return Result of remove operation. true - Remove success. false - Remove failed.
 */
bool DataSet::remove(const char *label){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            if ( !this->dataset.remove(index) ){
                this->error_number = Error_DataSetError;
                return false;
            }
            delete[] data->label;
            data->label = nullptr;
            data->value = nullptr;
            this->error_number = Error_None;
            return true;
        }
    }
    this->error_number = Error_LabelNotExist;
    return false;
}

/************
 *  @fn     bool DataSet::set(const char *label,bool value)
 *  @brief  Set data to new value. Must have same type.
 *  @param  label   Data label.
 *  @param  value   New data value.
 *  @return Result of set operation. true - Set success. false - Set failed.
 */
bool DataSet::set(const char *label,bool value){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            if ( data->data_type != DataType::Boolean ){
                this->error_number = Error_DataTypeError;
                return false;
            }
            memcpy(data->value,&value,sizeof(bool));
            this->error_number = Error_None;
            return true;
        }
    }

    this->error_number = Error_LabelNotExist;
    return false;
}

/************
 *  @fn     bool DataSet::set(const char *label,double value)
 *  @brief  Set data to new value. Must have same type.
 *  @param  label   Data label.
 *  @param  value   New data value.
 *  @return Result of set operation. true - Set success. false - Set failed.
 */
bool DataSet::set(const char *label,double value){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            if ( data->data_type != DataType::Number ){
                this->error_number = Error_DataTypeError;
                return false;
            }
            memcpy(data->value,&value,sizeof(double));
            this->error_number = Error_None;
            return true;
        }
    }

    this->error_number = Error_LabelNotExist;
    return false;
}

/************
 *  @fn     bool DataSet::set(const char *label,const char *value)
 *  @brief  Set data to new value. Must have same type.
 *  @param  label   Data label.
 *  @param  value   New data value.
 *  @return Result of set operation. true - Set success. false - Set failed.
 */
bool DataSet::set(const char *label,const char *value){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    if ( value == nullptr ){
        this->error_number = Error_NullString;
        return false;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            int old_malloc_size = data->label_size + data->value_size;
            int new_malloc_size = data->label_size + strlen(value) + 1;
            if ( new_malloc_size > old_malloc_size ){
                /* Need to realloc memory. */
                    unsigned char *buffer = new unsigned char[new_malloc_size];
                if ( buffer == nullptr ){
                    this->error_number = Error_MallocFailed;
                    return false;
                }
                strcpy((char*)buffer,data->label);
                data->label = (char*)buffer;
                data->value = (char*)buffer + data->label_size;
                data->value_size = strlen(value) + 1;
                strncpy((char*)data->value,value,data->value_size);
            }else{
                /* Not need to realloc memory. */
                data->value_size = strlen(value) + 1;
                strncpy((char*)data->value,value,data->value_size);
            }
            this->error_number = Error_None;
            return true;
        }
    }
    this->error_number = Error_LabelNotExist;
    return false;
}

/************
 *  @fn     bool DataSet::getBoolean(const char *label)
 *  @brief  Get data value of boolean data. If data type not match, will return false.
 *  @param  label   Data label.
 *  @return Data value.
 */
bool DataSet::getBoolean(const char *label){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            if ( data->data_type != DataType::Boolean ){
                this->error_number = Error_DataTypeError;
                return false;
            }
            this->error_number = Error_None;
            bool ret_value = false;
            memcpy(&ret_value,data->value,sizeof(bool));
            return ret_value;
        }
    }
    this->error_number = Error_LabelNotExist;
    return false;
}

/************
 *  @fn     double DataSet::getNumber(const char *label)
 *  @brief  Get data value of number data. If data type not match, will return 0.
 *  @param  label   Data label.
 *  @return Data value.
 */
double DataSet::getNumber(const char *label){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return 0.0;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            if ( data->data_type != DataType::Number ){
                this->error_number = Error_DataTypeError;
                return 0.0;
            }
            this->error_number = Error_None;
            double ret_value = 0.0;
            memcpy(&ret_value,data->value,sizeof(double));
            return ret_value;
        }
    }
    this->error_number = Error_LabelNotExist;
    return 0.0;
}

/************
 *  @fn     const char *DataSet::getString(const char *label)
 *  @brief  Get data value of string data. If data type not match, will return nullptr.
 *  @param  label   Data label.
 *  @return Data value.
 */
const char *DataSet::getString(const char *label){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return nullptr;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            if ( data->data_type != DataType::String ){
                this->error_number = Error_DataTypeError;
                return nullptr;
            }
            this->error_number = Error_None;
            if ( data->value_size > 0 ){
                return (const char*)(data->value);
            }else{
                return nullptr;
            }
            
        }
    }
    this->error_number = Error_LabelNotExist;
    return nullptr;
}

/************
 *  @fn     bool DataSet::hasLabel(const char *label)
 *  @brief  Check label existance.
 *  @param  label   Data label.
 *  @return Label existance. true - Label exist. false - Label not exist.
 */
bool DataSet::hasLabel(const char *label){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return false;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            this->error_number = Error_None;
            return true;
        }
    }
    this->error_number = Error_None;
    return false;
}

/************
 *  @fn     DataSet::DataType DataSet::getDataType(const char *label)
 *  @brief  Get data type.
 *  @param  label   Data label.
 *  @return Data type. If label not exist, it will return DataType::Invaild.
 */
DataSet::DataType DataSet::getDataType(const char *label){
    if ( label == nullptr ){
        this->error_number = Error_NullLabel;
        return DataType::Invaild;
    }
    Data *data = nullptr;
    for ( int index = 0; index < this->dataset.getSize(); index++ ){
        data = this->dataset.get(index);
        if ( data == nullptr ) continue;
        if ( strcmp(data->label,label) == 0 ){
            this->error_number = Error_None;
            return data->data_type;
        }
    }
    this->error_number = Error_LabelNotExist;
    return DataType::Invaild;
}

/************
 *  @fn     bool DataSet::clear(void)
 *  @brief  Clear all data.
 *  @param  None
 *  @return Operation result. true - Clear success. false - Clear failed.
 */
bool DataSet::clear(void){
    while ( this->dataset.getSize() > 0 ){
        Data *data = this->dataset.get(0);
        if ( data != nullptr ){
            if ( data->label != nullptr ){
                delete[] data->label;
            }
            delete data;
        }
        this->dataset.remove(0);
    }
    this->error_number = Error_None;
    return true;
}

/************
 *  @fn     int DataSet::getDataCount(void)
 *  @brief  Get data count in this dataset.
 *  @param  None
 *  @return Data count.
 */
int DataSet::getDataCount(void){
    this->error_number = Error_None;
    return this->dataset.getSize();
}

/************
 *  @fn     struct DataSet::Data *DataSet::getDataItem(int pos)
 *  @brief  Get data item with pos in list.
 *  @param  pos Data pos in dataset.
 *  @return Pointer of dataset, it will return nullptr if pos not vaild.
 */
struct DataSet::Data *DataSet::getDataItem(int pos){
    if ( pos >= this->dataset.getSize() ){
        this->error_number = Error_IllegalIndex;
        return nullptr;
    }
    this->error_number = Error_None;
    return this->dataset.get(pos);
}

/************
 *  @fn     DataSet::ErrorNumber DataSet::getErrorNumber(void)
 *  @brief  Get error number of last operation.
 *  @param  None
 *  @return Error number of last operation.
 */
int DataSet::getErrorNumber(void){
    return this->error_number;
}